Alternatives
Video Summary
In the useMemo lesson, we saw how the useMemo
hook can cache the result of an expensive computation, like when generating prime numbers:
function App() { const [selectedNum, setSelectedNum] = React.useState(100); const time = useTime();
const allPrimes = React.useMemo(() => { const result = [];
for (let counter = 2; counter < selectedNum; counter++) { if (isPrime(counter)) { result.push(counter); } }
return result; }, [selectedNum]);
return ( <> <p className="clock"> {format(time, 'hh:mm:ss a')} </p> <form> <label htmlFor="num">Your number:</label> <input type="number" value={selectedNum} onChange={(event) => { // To prevent computers from exploding, // we'll max out at 100k let num = Math.min(100_000, Number(event.target.value));
setSelectedNum(num); }} /> </form> <p> There are {allPrimes.length} prime(s) between 1 and {selectedNum}: {' '} <span className="prime-list"> {allPrimes.join(', ')} </span> </p> </> );}
This is perfectly valid, but if we take a step back and look at this component... Are we really solving this problem in the best way?
This App
component appears to be managing two entirely unrelated tasks:
- Running a digital clock.
- The prime number stuff, picking a number and showing the related primes.
What if we split them off into two separate components?
For example:
Code Playground
- PrimeCalculator render
- PrimeCalculator render
I've extracted two new components, Clock
and PrimeCalculator
. Because they're siblings now, updates in Clock
won't affect PrimeCalculator
.
Here's a graph showing this dynamic. Each box represents a component instance, and they flash when they re-render. Try clicking the “Increment” button to see it in action:
App
Clock
time: 22:35:27
PrimeCalculator
selectedNum: 10
We hear a lot about lifting state up, but sometimes, the better approach is to push state down! Each component should have a single responsibility, and in the example above, App
was doing two totally-unrelated things.
Now, this won't always be an option. In a large, real-world app, there's lots of state that needs to be lifted up pretty high, and can't be pushed down.
I have another trick up my sleeves for this situation.
Let's look at an example. Suppose we need the time
variable to be lifted up, above PrimeCalculator
:
Code Playground
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
- PrimeCalculator render
We use the time
variable to control the background color. To make this possible, we've lifted the time
state up to the parent App
component.
With this change, PrimeCalculator
will once again render whenever time
changes, once per second.
We could solve this with the useMemo
hook once more, but I have another idea. What if we memoize the entire component?
// PrimeCalculator.jsfunction PrimeCalculator() { // Unchanged}
export default React.memo(PrimeCalculator);
As we learned in the “Why React Re-Renders” lesson, React.memo
wraps around our component like a forcefield, protecting it from unrelated updates. Our PrimeCalculator
component will only re-render when its props or state changes.
And because it doesn't have any props, the only way this component can re-render is if the selectedNum
state changes.
There's an interesting perspective-shift here: Before, we were memoizing the result of a specific computation, calculating the prime numbers. In this case, however, I've memoized the entire component instead.
Either way, the expensive calculation stuff will only re-run when the user picks a new selectedNum
. But we've optimized the parent component rather than the specific slow lines of code.
Here's an updated graph, showing what's happening here:
App
time: 22:35:27
Clock
PrimeCalculator
selectedNum: 10
Pure Component
To be clear, I'm not suggesting that useMemo
is bad, or that we should always try and find alternatives. The point I'm hoping to make is that we should pay attention to application structure; when we refactor code to be more readable, we often get performance benefits for free, and it might turn out that useMemo
is not necessary!
That said, useMemo
is still a valuable tool in the toolbox. On this course platform, I use useMemo
a couple dozen times!
I realize that this lesson is pretty advanced — over time, in your React journey, you'll start to see opportunities to refactor code and improve structure, but it takes a lot of practice to reach this point. I'm hoping that some of these ideas will percolate in the back of your mind, as we continue to work through the course.
Here's the interactive graphs from the video above:
Sibling Clock and PrimeCalculator:
App
Clock
time: 22:35:27
PrimeCalculator
selectedNum: 10
Pure PrimeCalculator with lifted time
state:
App
time: 22:35:27
Clock
PrimeCalculator
selectedNum: 10
Pure Component